package com.dsteiner.widgets.client.controller;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;

import com.dsteiner.widgets.client.SuggestBoxWidget.SuggestBoxDataProvider;
import com.dsteiner.widgets.client.SuggestBoxWidget.Suggestion;
import com.dsteiner.widgets.client.SuggestBoxWidget.SuggestionRequest;
import com.dsteiner.widgets.client.SuggestBoxWidget.SuggestionResult;
import com.dsteiner.widgets.client.Widgets.IFlowPanel;
import com.dsteiner.widgets.client.Widgets.IHTML;
import com.dsteiner.widgets.client.Widgets.ILazyWidget;
import com.dsteiner.widgets.client.Widgets.IWidget;
import com.dsteiner.widgets.client.Widgets.IWidgetBuilder;
import com.dsteiner.widgets.client.Widgets.WidgetListenerAdapter;
import com.dsteiner.widgets.client.model.SuggestBoxModel;
import com.dsteiner.widgets.client.model.SuggestionModel;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.http.client.Request;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;

public class SuggestBoxController implements NativePreviewHandler{

	private SuggestBoxModel suggestBoxModel;
	private IFlowPanel popup;
	private SuggestBoxDataProvider suggestBoxDataProvider;
	private Request requestSuggestions;
	private IWidget loadingHtml;
	private IWidgetBuilder suggestWidgetBuilder;
	private IHTML noResultsHtml;
	private List<SuggestionModel> suggestionModels = new ArrayList<SuggestionModel>();

	public void registerLoadingHtml(IWidget loadingHtml) {
		this.loadingHtml = loadingHtml;
	}
	
	public void registerNoResultsHtml(IHTML noResultsHtml) {
		this.noResultsHtml = noResultsHtml;
	}

	public void registerPopup(IFlowPanel popup) {
		this.popup = popup;
		popup.setVisible(false);
	}

	public void registerSuggestBoxDataProvider(SuggestBoxDataProvider suggestBoxDataProvider) {
		this.suggestBoxDataProvider = suggestBoxDataProvider;
	}

	public void registerSuggestBoxModel(final SuggestBoxModel suggestBoxModel) {
		this.suggestBoxModel = suggestBoxModel;
		suggestBoxModel.addPropertyChangeListener("requestedText", new PropertyChangeListener(){
			public void propertyChange(PropertyChangeEvent evt) {
				updateSuggestions();
				popup.clear();
				popup.add(loadingHtml);
//				popup.setVisible(true);
			}
		});
		suggestBoxModel.addPropertyChangeListener("requestedSuggestions", new PropertyChangeListener(){
			public void propertyChange(PropertyChangeEvent evt) {
				updateSuggestionsPopup();
				updateNoResultsVisibility();
				updatePopupVisibility();
			}
		});
		suggestBoxModel.addPropertyChangeListener("choosenSuggestion", new PropertyChangeListener(){
			public void propertyChange(PropertyChangeEvent evt) {
				SuggestionModel choosenSuggestion = suggestBoxModel.getChoosenSuggestion();
				if(choosenSuggestion != null){
					suggestBoxModel.setText(choosenSuggestion.getSuggestion().getDisplayText());
					suggestBoxModel.setSelectedSuggestion(null);
					suggestBoxModel.setHasFocus(false);
				}
			}
		});
		suggestBoxModel.addPropertyChangeListener("selectedSuggestion", new PropertyChangeListener(){
			public void propertyChange(PropertyChangeEvent evt) {
				updatePopupVisibility();
			}
		});
		suggestBoxModel.addPropertyChangeListener("text", new PropertyChangeListener(){
			public void propertyChange(PropertyChangeEvent evt) {
				updateRequestedText();
				DeferredCommand.addCommand(new Command(){
					public void execute() {
						updatePopupVisibility();
					}
				});
			}
		});
		suggestBoxModel.addPropertyChangeListener("hasFocus", new PropertyChangeListener(){
			private HandlerRegistration addNativePreviewHandler;
			public void propertyChange(PropertyChangeEvent evt) {
				updatePopupVisibility();
				if(suggestBoxModel.hasFocus()){
					System.out.println("SuggestBox:addEventPreview");
					addNativePreviewHandler = Event.addNativePreviewHandler(SuggestBoxController.this);
				}
				else if(!suggestBoxModel.hasFocus() && addNativePreviewHandler != null){
					System.out.println("SuggestBox:removeEventPreview");
					addNativePreviewHandler.removeHandler();
				}
			}
		});
	}

	private void doSelectSuggestion(int nextIndex) {
		SuggestionModel selectedSuggestion = suggestBoxModel.getSelectedSuggestion();
		int indexOf = suggestionModels.indexOf(selectedSuggestion);
		System.out.println("SuggestBoxController.doSelectSuggestion() currentIndex:"+indexOf);
		if(indexOf == -1){
			if(nextIndex > 0)
				indexOf = 0;
			else if(nextIndex < 0)
				indexOf = suggestionModels.size() - 1;
		}
		else{
			indexOf = Math.min(suggestionModels.size() - 1, Math.max(0, indexOf + nextIndex));
		}
		System.out.println("SuggestBoxController.doSelectSuggestion() nextIndex:"+indexOf);
		SuggestionModel nextSuggestionModel = suggestionModels.get(indexOf);
		suggestBoxModel.setSelectedSuggestion(nextSuggestionModel);
	}

	public void registerSuggestBoxWidget(ILazyWidget suggestBoxWidget) {
		suggestBoxWidget.addWidgetListener(new WidgetListenerAdapter() {
			@Override
			public void onAttach(IWidget widget) {
			}
		});
	}

	public void registerSuggestionWidgetBuilder(IWidgetBuilder suggestWidgetBuilder) {
		this.suggestWidgetBuilder = suggestWidgetBuilder;
	}

	private void updateNoResultsVisibility() {
		for (SuggestionModel suggestionModel : suggestionModels) {
			if(suggestionModel.isVisible()){
				noResultsHtml.setVisible(false);
				return;
			}
		}
		noResultsHtml.setVisible(true);
	}

	private void updatePopupVisibility() {
		boolean hasFocus = suggestBoxModel.hasFocus() || suggestBoxModel.getSelectedSuggestion() != null;
		String text = suggestBoxModel.getText();
		List<SuggestionModel> visibleSuggestionModels = new ArrayList<SuggestionModel>();
		for (SuggestionModel suggestionModel : suggestionModels) {
			if(suggestionModel.isVisible())
				visibleSuggestionModels.add(suggestionModel);
		}
		boolean hasSuggestions = visibleSuggestionModels.size() > 0;
		boolean hasText = hasText();
		boolean isSuggestion = visibleSuggestionModels.size() == 1 && 
		suggestionModels.get(0).getSuggestion().getDisplayText().equals(text);
		
		boolean popupVisible = hasSuggestions && hasFocus && !isSuggestion && hasText;
		popup.setVisible(popupVisible);
	}

	private void updateRequestedText() {
		String text = suggestBoxModel.getText();
		if(!hasText())
			return;
		
		String requestedText = suggestBoxModel.getRequestedText();
		List<Suggestion> requestedSuggestions = suggestBoxModel.getRequestedSuggestions();
		if(requestedText != null && text.contains(requestedText) && 
				(requestedSuggestions == null || requestedSuggestions.isEmpty()))
			return;
		
		suggestBoxModel.setRequestedText(text);
	}

	private boolean hasText() {
		String text = suggestBoxModel.getText();
		return text != null && text.length() >= 3;
	}

	private void updateSuggestions() {
		if(requestSuggestions != null)
			requestSuggestions.cancel();
		
		String requestedText = suggestBoxModel.getRequestedText();
		requestSuggestions = suggestBoxDataProvider.requestSuggestions(new SuggestionRequest(requestedText), new AsyncCallback<SuggestionResult>(){
			public void onFailure(Throwable caught) {
				caught.printStackTrace();
			}
			public void onSuccess(SuggestionResult result) {
				List<Suggestion> suggestions = result.getSuggestions();
				suggestBoxModel.setRequestedSuggestions(suggestions);
				updateSuggestionsPopup();
				updateNoResultsVisibility();
			}
		});
		
	}

	private void updateSuggestionsPopup() {
		popup.clear();
		suggestionModels.clear();
		popup.add(noResultsHtml);
		List<Suggestion> requestedSuggestions = suggestBoxModel.getRequestedSuggestions();
		for (Suggestion suggestion : requestedSuggestions) {
			SuggestionModel suggestionModel = new SuggestionModel();
			suggestionModel.addPropertyChangeListener("visible", new PropertyChangeListener(){
				public void propertyChange(PropertyChangeEvent evt) {
					updateNoResultsVisibility();				
				}
			});
			suggestionModel.setSuggestion(suggestion);
			suggestionModels.add(suggestionModel);
			IWidget createWidget = suggestWidgetBuilder.createWidget(suggestionModel);
			popup.add(createWidget);
		}
//		popup.setVisible(true);
	}

	public void onPreviewNativeEvent(NativePreviewEvent event) {
		NativeEvent nativeEvent = event.getNativeEvent();
		int keyCode = nativeEvent.getKeyCode();
		int typeInt = event.getTypeInt();
		if(keyCode == KeyCodes.KEY_DOWN && typeInt == Event.ONKEYDOWN){
			doSelectSuggestion(1);
		}
		if(keyCode == KeyCodes.KEY_UP && typeInt == Event.ONKEYDOWN){
			doSelectSuggestion(-1);
		}
		if(keyCode == KeyCodes.KEY_ENTER && typeInt == Event.ONKEYDOWN){
			SuggestionModel selectedSuggestion = suggestBoxModel.getSelectedSuggestion();
			suggestBoxModel.setChoosenSuggestion(selectedSuggestion);
		}
	}
}